﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
using Enlightener.Engine.Structs__Enums___Interfaces;
using Enlightener.Engine.Utils;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Enlightener.Engine.Deobfuscator.Tasks._1_8
{
    public class ConstantsDecryptor : DeobfuscationTask
    {
        private class DecryptionMethod
        {
            public int[] Modifiers;
            public MethodDefinition Method;
        }

        private class DecryptionEntry
        {
            public DecryptionMethod TargetDecryptor;
            public MethodDefinition Source;

            public int Key1;
            public int Key2;
            public int InsertIndex;

            public string PlainText;

            public List<Instruction> BadInstructions;
        }

        private static Stream _resStream;
        private static Assembly _targetAsm;

        public ConstantsDecryptor(AssemblyDefinition asmDef)
            : base(asmDef)
        {
        }

        [DeobfuscationPhase(1, "Locate and load compressed resource")]
        public static bool Phase1()
        {
            var ctor =
                AsmDef.MainModule.Types.First(t => t.Name == "<Module>").Methods.FirstOrDefault(
                    m => m.Body.Instructions.Count >= 1 && m.Name == ".cctor");
            var resID = ctor.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 0);

            if(resID == 0)
            {
                PhaseError = new PhaseError
                                 {
                                     Level = PhaseError.ErrorLevel.Minor,
                                     Message = "No constants encryption?"
                                 };
                EmergencyCancel = true;
                return true;
            }

            _targetAsm = Assembly.LoadFile(DeobfuscatorContext.InPath);
            PhaseParam = _targetAsm.GetManifestResourceStream(Encoding.UTF8.GetString(BitConverter.GetBytes(resID)));

            return true;
        }

        /// <summary>
        /// PhaseParam = resStream
        /// </summary>
        [DeobfuscationPhase(2, "Decompress resource")]
        public static bool Phase2()
        {
            var resStream = new DeflateStream(PhaseParam, CompressionMode.Decompress);
            var ms = new MemoryStream();

            byte[] buffer = new byte[4096];
            int count = resStream.Read(buffer, 0, 4096);
            do
            {
                ms.Write(buffer, 0, count);
                count = resStream.Read(buffer, 0, 4096);
            } while (count != 0);

            resStream.Dispose();

            _resStream = ms;

            return true;
        }

        [DeobfuscationPhase(3, "Locate and index decryption methods")]
        public static bool Phase3()
        {
            var decMethods = AsmDef.FindMethods(IsDecryptor).Select(mDef => new DecryptionMethod
                                                                                {
                                                                                    Modifiers = ParseModifiers(mDef), Method = mDef
                                                                                }).ToList();
            if(decMethods.Count == 0)
            {
                PhaseError = new PhaseError
                                 {
                                     Level = PhaseError.ErrorLevel.Minor,
                                     Message = "No constant encryption?"
                                 };
                EmergencyCancel = true;
                return true;
            }

            PhaseParam = decMethods;

            return true;
        }

        /// <summary>
        /// PhaseParam = decMethods
        /// </summary>
        [DeobfuscationPhase(4, "Construct decryption entries")]
        public static bool Phase4()
        {
            var decMethods = PhaseParam as List<DecryptionMethod>;
            var decEntries = new List<DecryptionEntry>();

            foreach(var mDef in AsmDef.FindMethods(m => true))
            {
                if (!mDef.HasBody)
                    continue;

                for (int i = 0; i < mDef.Body.Instructions.Count; i++)
                {
                    var instr = mDef.Body.Instructions[i];

                    if (instr.OpCode == OpCodes.Call &&
                        decMethods.FirstOrDefault(md => md.Method == (instr.Operand as MethodReference).Resolve()) !=
                        null)
                    {
                        var tmpEntry = new DecryptionEntry
                                           {
                                               Key1 = (int) instr.Previous.Previous.Operand,
                                               Key2 = (int) instr.Previous.Operand,
                                               TargetDecryptor =
                                                   decMethods.First(
                                                       md => md.Method == (instr.Operand as MethodReference).Resolve()),
                                               InsertIndex = i,
                                               BadInstructions = mDef.Body.Instructions.GetInstructionBlock(i - 2, 4),
                                               Source = mDef
                                           };

                        decEntries.Add(tmpEntry);
                    }
                }
            }

            var test = DecryptString(decEntries[0]);

            return true;
        }

        private static bool IsDecryptor(MethodDefinition mDef)
        {
            if (!mDef.HasBody)
                return false;

            if (mDef.Body.Variables.Count != 20)
                return false;

            if (mDef.Parameters.Count != 2)
                return false;

            return true;
        }
        private static int[] ParseModifiers(MethodDefinition mDef)
        {
            var modifiers = new int[5];

            modifiers[0] = mDef.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 0);
            modifiers[1] = mDef.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 1);
            modifiers[2] = mDef.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 2);
            modifiers[3] = mDef.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 21);
            modifiers[4] = mDef.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 22);

            return modifiers;
        }

        #region Reversed Methods

        //Copyright (C) 2011 Ki

        //This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
        //This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
        //You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
        //Linking Confuser statically or dynamically with other modules is making a combined work based on Confuser. Thus, the terms and conditions of the GNU General Public License cover the whole combination.
        //In addition, as a special exception, the copyright holders of Confuser give you permission to combine a portion of Confuser with other binary code ("Program") under any license the copyright holders of Program specified, provided the combined work is produced by Confuser.

        private static object DecryptString(DecryptionEntry entry) //uint a, uint b)
        {
            object obj2;
            MethodBase currentMethod = MethodBase.GetCurrentMethod();
            uint num = (uint) (currentMethod.MetadataToken ^ (currentMethod.DeclaringType.MetadataToken*entry.Key1));
            uint num2 = (uint)entry.TargetDecryptor.Modifiers[0] ^ num;
            uint num3 = (uint)entry.TargetDecryptor.Modifiers[1];
            uint num4 = (uint)entry.TargetDecryptor.Modifiers[2];

            for (uint i = 1; i <= 64; i++)
            {
                num2 = (uint) (((num2 & 16777215) << 8) | ((num2 & -16777216) >> 24));
                uint num6 = (num2 & 255)%64;
                if ((num6 >= 0) && (num6 < 16))
                {
                    num3 |= ((uint) (((num2 & 65280) >> 8) & ((num2 & 16711680) >> 16))) ^ (~num2 & 255);
                    num4 ^= ((num2*i) + 1)%16;
                    num2 += (num3 | num4) ^ (uint)entry.TargetDecryptor.Modifiers[3];
                }
                else if ((num6 >= 16) && (num6 < 32))
                {
                    num3 ^= (uint) (((num2 & 16711935) << 8) ^ (((num2 & 16776960) >> 8) | (~num2 & 65535)));
                    num4 += (num2*i)%32;
                    num2 |= (num3 + ~num4) & (uint)entry.TargetDecryptor.Modifiers[3];
                }
                else if ((num6 >= 32) && (num6 < 48))
                {
                    num3 += ((num2 & 255) | ((uint) ((num2 & 16711680) >> 16))) + (~num2 & 255);
                    num4 -= ~(num2 + num6)%48;
                    num2 ^= (num3 % num4) | (uint)entry.TargetDecryptor.Modifiers[3];
                }
                else if ((num6 >= 48) && (num6 < 64))
                {
                    num3 ^= (uint) ((((num2 & 16711680) >> 16) | ~(num2 & 255))*(~num2 & 16711680));
                    num4 += (num2 ^ (i - 1))%num6;
                    num2 -= ~(num3 ^ num4) + (uint)entry.TargetDecryptor.Modifiers[3];
                }
            }
            uint key = num2 ^ (uint)entry.Key2;

            byte num8;
            byte[] buffer;

            BinaryReader reader = new BinaryReader(_resStream);
            reader.BaseStream.Seek((long) key, SeekOrigin.Begin);
            num8 = reader.ReadByte();
            buffer = reader.ReadBytes(reader.ReadInt32());

            byte[] buffer2 =
                _targetAsm.GetModule(entry.TargetDecryptor.Method.Module.Name).ResolveSignature(
                    entry.TargetDecryptor.Modifiers[4] ^
                    entry.TargetDecryptor.Method.
                        MetadataToken.ToInt32());

            uint num9 = (key + num8)*1192117104;
            ushort num10 = (ushort) (num9 >> 16);
            ushort num11 = (ushort) (num9 & 65535);
            ushort num12 = num11;
            ushort num13 = num10;
            for (int j = 0; j < buffer.Length; j++)
            {
                buffer[j] = (byte) (buffer[j] ^ ((byte) ((((num9*num12) + num13)%256) ^ buffer2[j%8])));
                num12 = (ushort) (((num9*num12) + num10)%65536);
                num13 = (ushort) (((num9*num13) + num11)%65536);
            }

            obj2 = Encoding.UTF8.GetString(buffer);

            return obj2;
        }

        #endregion
    }
}